# import self
from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QSplitter, \
    QPushButton, QFileDialog, QLineEdit, QMenu, QTextEdit, QDialog,QLabel,QGroupBox,QStyle
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QPoint, Qt, QRectF, QTimer,QRect,QSize
from PyQt5.QtGui import QPainter, QColor, QPen, QBrush, QIcon, QPixmap, QFont, QTransform
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QPoint, Qt, QRectF, QThreadPool, QRunnable  # 添加 QRunnable
from port_scan import scan_ports
import socket
import random
import pandas as pd
import asyncio
import math
from concurrent.futures import ThreadPoolExecutor
import nmap
import geoip2.database
from ip2geotools.databases.noncommercial import DbIpCity
import pyshark
import subprocess
import psutil
# PyQt5的主窗口类
class MainWindow(QMainWindow):
    update_table_signal = pyqtSignal(str)  # 新增的信号，用于更新表格

    def __init__(self, canvas):
        super().__init__()
        self.canvas = canvas
        central_widget = QWidget()
        main_layout = QVBoxLayout(central_widget)  # 主布局
        
        # 创建一个水平布局来放置导出按钮
        top_toolbar = QHBoxLayout()
        top_toolbar.setAlignment(Qt.AlignCenter)  # 居中对齐
        top_toolbar.setSpacing(10)  # 设置控件间距为10

        # 创建输入框和按钮
        self.ip_input = QLineEdit()
        self.ip_input.setPlaceholderText("请输入IP地址")
        self.ip_input.setMinimumSize(120, 25)  # 设置最小宽度为120像素，最小高度为25像素
        self.ip_input.setMaximumSize(200, 40)  # 设置最大宽度为200像素，最大高度为40像素
        self.startButton = QPushButton("运行")
        self.startButton.setMinimumSize(80, 25)  # 设置最小宽度为80像素，最小高度为25像素
        self.startButton.setMaximumSize(120, 40)  # 设置最大宽度为120像素，最大高度为40像素
        top_toolbar.addWidget(self.ip_input)
        top_toolbar.addWidget(self.startButton)

        # 添加暂停/继续按钮
        self.pause_button = QPushButton("暂停")
        self.pause_button.setCheckable(True)  # 使按钮可以切换状态
        self.pause_button.setMinimumSize(80, 25)  # 设置最小宽度为80像素，最小高度为25像素
        self.pause_button.setMaximumSize(120, 40)  # 设置最大宽度为120像素，最大高度为40像素
        self.pause_button.clicked.connect(self.toggle_pause)
        top_toolbar.addWidget(self.pause_button)

        # 创建分组框
        other_group = QGroupBox("其他操作")
        other_layout = QHBoxLayout()
        export_button = QPushButton(QIcon("export_icon.png"), "导出为Excel")  # 添加图标
        export_button.setMinimumSize(80, 25)  # 设置最小宽度为80像素，最小高度为25像素
        export_button.setMaximumSize(120, 40)  # 设置最大宽度为120像素，最大高度为40像素
        export_button.clicked.connect(self.export_to_excel)
        other_layout.addWidget(export_button)
        logging_button = QPushButton(QIcon("logging_icon.png"), "Logging")  # 添加图标
        logging_button.setMinimumSize(80, 25)  # 设置最小宽度为80像素，最小高度为25像素
        logging_button.setMaximumSize(120, 40)  # 设置最大宽度为120像素，最大高度为40像素
        logging_button.clicked.connect(self.show_logging)
        other_layout.addWidget(logging_button)
        other_group.setLayout(other_layout)
        top_toolbar.addWidget(other_group)

        # 创建分组框
        zoom_group = QGroupBox("缩放操作")
        zoom_layout = QHBoxLayout()
        zoom_in_button = QPushButton(QIcon(r"D:\tkinter\Tkinter-Designer-master\pyfuxian\net6.0-windows\ico\PriorityHigh.ico"), "放大")  # 添加图标
        zoom_in_button.setMinimumSize(80, 25)  # 设置最小宽度为80像素，最小高度为25像素
        zoom_in_button.setMaximumSize(120, 40)  # 设置最大宽度为120像素，最大高度为40像素
        zoom_in_button.clicked.connect(self.canvas.zoom_in)
        zoom_layout.addWidget(zoom_in_button)
        zoom_out_button = QPushButton(QIcon(r"D:\tkinter\Tkinter-Designer-master\pyfuxian\net6.0-windows\ico\PriorityLow.ico"), "缩小")  # 添加图标
        zoom_out_button.setMinimumSize(80, 25)  # 设置最小宽度为80像素，最小高度为25像素
        zoom_out_button.setMaximumSize(120, 40)  # 设置最大宽度为120像素，最大高度为40像素
        zoom_out_button.clicked.connect(self.canvas.zoom_out)
        zoom_layout.addWidget(zoom_out_button)
        zoom_group.setLayout(zoom_layout)
        top_toolbar.addWidget(zoom_group)

        # 将水平布局添加到主布局的顶部
        main_layout.addLayout(top_toolbar)
        # 创建一个红叉按钮
        self.close_button = QPushButton("×", self)
        self.close_button.setStyleSheet("QPushButton { font-size: 24px; color: red; background-color: transparent; border: none; }")
        self.close_button.clicked.connect(self.close)  # 绑定点击事件

        # 设置按钮的位置到窗口的右上角
        self.close_button.move(self.width() - 30, 10)  # 距离右边缘30像素，距离顶部10像素

        self.setCentralWidget(central_widget)

        # 添加查询按钮
        self.queryButton = QPushButton("查询")
        self.queryButton.setMinimumSize(80, 25)  # 设置最小宽度为80像素，最小高度为25像素
        self.queryButton.setMaximumSize(120, 40)  # 设置最大宽度为120像素，最大高度为40像素
        self.queryButton.clicked.connect(self.filter_table_by_ip)  # 绑定点击事件
        top_toolbar.addWidget(self.queryButton)

        # 使用 QSplitter 管理 Canvas 和 Table 的布局
        splitter = QSplitter(Qt.Vertical)
        splitter.addWidget(self.canvas)
        splitter.addWidget(self.canvas.table)
        splitter.setStretchFactor(0, 7)  # 画布占据更多空间
        splitter.setStretchFactor(1, 3)  # 表格占据较少空间

        # 将 QSplitter 添加到主布局中
        main_layout.addWidget(splitter)

        self.setCentralWidget(central_widget)
        self.canvas.setMinimumHeight(100)  # 设置 Canvas 的最小高度
        self.canvas.table.setMinimumHeight(100)  # 设置表格的最小高度
        self.setWindowTitle("Network Scanner")
        self.setGeometry(100, 100, 800, 600)
        self.setStyleSheet("background-color: white;")  # 设置背景颜色
        self.setFont(QFont("Arial", 10))  # 设置字体大小和样式
        # 设置窗口为全屏
        # self.showFullScreen()
    def closeEvent(self, event):
        """重写关闭事件处理函数"""
        event.accept()  # 确保窗口可以关闭
    def mouseDoubleClickEvent(self, event):
        """重写鼠标双击事件处理函数"""
        if event.button() == Qt.LeftButton:
            # 获取双击位置的全局坐标
            global_pos = event.globalPos()
            # 获取窗口标题栏的矩形区域
            title_bar_rect = self.titleBarRect()
            # 检查双击位置是否在标题栏区域内
            if title_bar_rect.contains(global_pos):
                # 检查当前窗口是否处于全屏状态
                if self.isFullScreen():
                    # 恢复到指定大小
                    self.setGeometry(100, 100, 800, 600)
                else:
                    # 获取当前窗口的几何位置
                    current_geometry = self.geometry()
                    # 检查当前窗口是否为指定大小
                    if current_geometry.width() == 800 and current_geometry.height() == 600:
                        self.showFullScreen()  # 切换到全屏模式
                    else:
                        self.setGeometry(100, 100, 800, 600)  # 设置为指定大小

    def titleBarRect(self):
        """获取窗口标题栏的矩形区域"""
        title_bar_height = self.style().pixelMetric(QStyle.PM_TitleBarHeight, None, self)
        return QRect(self.geometry().topLeft(), QSize(self.width(), title_bar_height))
    def filter_table_by_ip(self):
        """根据输入的IP地址筛选表格中的信息"""
        ip = self.ip_input.text().strip()
        if not ip:
            return  # 如果输入框为空，则不进行筛选

        self.canvas.table.clearContents()  # 清空表格内容
        self.canvas.table.setRowCount(0)  # 重置行数

        for ip_addr, node_info in self.canvas.selected_nodes.items():
            if ip_addr == ip:
                row_position = self.canvas.table.rowCount()
                self.canvas.table.insertRow(row_position)
                self.canvas.table.setItem(row_position, 0, QTableWidgetItem(ip_addr))
                self.canvas.table.setItem(row_position, 1, QTableWidgetItem(", ".join(map(str, node_info["open_ports"]))))
                self.canvas.table.setItem(row_position, 2, QTableWidgetItem(self.canvas.get_node_type(ip_addr)))
                self.canvas.table.setItem(row_position, 3, QTableWidgetItem(self.canvas.get_link_representation(ip_addr)))
                actions_item = QTableWidgetItem("; ".join([f"{port}: {self.canvas.common_ports.get(port, 'Unknown service')}" for port in node_info["open_ports"]]))
                self.canvas.table.setItem(row_position, 4, actions_item)
                break  # 只插入匹配的行    
    def show_logging(self):
        """显示日志内容"""
        log_dialog = QDialog(self)
        log_dialog.setWindowTitle("Logging")
        log_dialog.setGeometry(100, 100, 600, 400)

        log_text_edit = QTextEdit(log_dialog)
        log_text_edit.setReadOnly(True)
        log_text_edit.setText(self.canvas.get_log_content())

        log_dialog.setLayout(QVBoxLayout())
        log_dialog.layout().addWidget(log_text_edit)
        log_dialog.exec_()
    def toggle_pause(self):
        """切换暂停状态"""
        if self.pause_button.isChecked():
            self.pause_button.setText("继续")
            self.canvas.pause_updates()  # 暂停更新
        else:
            self.pause_button.setText("暂停")
            self.canvas.resume_updates()  # 恢复更新

    def export_to_excel(self):
        # 获取表格的行数和列数
        row_count = self.canvas.table.rowCount()
        col_count = self.canvas.table.columnCount()

        # 创建一个空的列表来存储表格数据
        data = []

        # 遍历表格中的每一行和每一列，获取单元格的内容
        for row in range(row_count):
            row_data = []
            for col in range(col_count):
                item = self.canvas.table.item(row, col)
                if item is not None:
                    row_data.append(item.text())
                else:
                    row_data.append('')
            data.append(row_data)

        # 将数据转换为 Pandas DataFrame
        df = pd.DataFrame(data,
                          columns=[self.canvas.table.horizontalHeaderItem(col).text() for col in range(col_count)])

        # 打开文件对话框，让用户选择保存路径
        file_path, _ = QFileDialog.getSaveFileName(self, "导出为Excel", "", "Excel Files (*.xlsx);;All Files (*)")

        if file_path:
            # 将 DataFrame 保存为 Excel 文件
            df.to_excel(file_path, index=False)
            print(f"表格已成功导出到: {file_path}")
        
        # 新增：获取筛选输入框内容并进行筛选
        subnet = self.ip_input.text()  # 假设输入框用于输入子网
        if subnet:
            self.canvas.filter_table_by_subnet(subnet)


    
class Canvas(QWidget):
    update_alive_nodes_signal = pyqtSignal(dict)
    update_trace_signal = pyqtSignal(list)

    def __init__(self):
        super().__init__()
        self.alive_nodes = {}  # 存储节点的IP地址和对应的QPoint
        self.selected_nodes = {}  # 用于记录每个节点的选中状态和IP地址显示状态
        self.dragging_node = None  # 当前正在拖拽的节点
        self.drag_start_pos = None  # 拖拽开始时的鼠标位置
        self.thread_pool = QThreadPool()  # 创建线程池
        self.update_alive_nodes_signal.connect(self.update_alive_nodes)
        self.update_trace_signal.connect(self.update_trace)

        # 创建一个空的表格控件
        self.table = QTableWidget()
        self.table.setColumnCount(5)  # 设置列数
        self.table.setHorizontalHeaderLabels(['IP Address', 'Open Ports', 'Type', 'Link', 'Actions'])
        self.table.setRowCount(0)  # 开始时没有行
        self.table.horizontalHeader().setStretchLastSection(True)  # 让最后一列尽可能宽

        # 加载图标
        self.icon = QIcon(r"D:\tkinter\Tkinter-Designer-master\pyfuxian\pic\Computer.ico")
        self.switch_icon = QIcon(r"D:\tkinter\Tkinter-Designer-master\pyfuxian\pic\net.ico")  # 交换机图标
        self.router_icon = QIcon(r"D:\tkinter\Tkinter-Designer-master\pyfuxian\pic\router.JPG")  # 路由器图标
        self.node_links = {}  # 新增：用于存储节点间的连接
        self.router_ips = {}  # 用于存储子网和对应路由器IP的映射
        self.router_positions = {}  # 用于存储子网和对应路由器位置的映射
        self.switch_ips = {}  # 用于存储子网和对应交换机IP的映射
        self.switch_positions = {}  # 用于存储子网和对应交换机位置的映射
        self.lines = {}  # 键为节点IP，值为 (QPoint, QPoint) 表示从节点到交换机的连线
        self.router_switch_lines = {}  # 新增：用于存储路由器和交换机之间的连
        self.trace_ll=[] # 路径表
        # 初始化本地主机的节点信息
        self.add_local_host()

        self.max_step = 5  # 每次移动的最大步长
        self.gravity_constant = 0.02  # 引力常数（稍微减小）
        self.repulsion_constant = 0.1  # 主机节点与交换机之间的斥力常数（稍微增大）
        self.node_repulsion_constant = 0.05  # 同一子网内主机节点之间的斥力常数
        self.min_distance = 150  # 主机节点与交换机之间斥力作用的最小距离
        self.max_distance = 150  # 引力作用的最大距离
        self.node_min_distance = 30  # 同一子网内主机节点之间斥力作用的最小距离
        self.jitter_magnitude = 2  # 抖动幅度（像素）
        self.jitter_frequency = 0.5  # 抖动频率（0 到 1 之间的概率）
        self.router_gravity_constant = 0.02  # 路由器引力常数，可根据需要调整
        self.router_repulsion_constant = 0.1  # 路由器与交换机之间的斥力常数，可根据需要调整
        self.router_min_distance = 150  # 路由器与交换机之间斥力作用的最小距离
        self.router_max_distance = 150  # 路由器与交换机之间引力作用的最大距离

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update_positions)
        self.timer.start(50)  # 每50毫秒更新一次位置
        self._is_paused = False  # 新增：暂停标志
        self.scale_factor = 1.0  # 初始缩放因子
        self.transform = QTransform()  # 用于存储当前的缩放和平移变换
        self.is_panning = False  # 标记是否正在平移
        self.pan_start_pos = None  # 平移的起始点
        self.dragging_node = None  # 正在拖拽的节点或交换机
        self.drag_start_pos = None  # 拖拽的起始位置

        self.log_content = ""  # 用于存储日志内容
        self.geoip_reader = geoip2.database.Reader(r"C:\Users\61609\Desktop\漏洞\GeoLite2-City.mmdb")

    common_ports = {
        20: "FTP数据传输（被动模式）",
        21: "FTP控制（主动模式）",
        22: "SSH（安全远程登录）",
        23: "Telnet（不安全的远程登录）",
        25: "SMTP（邮件发送）",
        53: "DNS（域名解析）",
        67: "DHCP（动态主机配置协议）",
        68: "DHCP客户端",
        80: "HTTP（网页浏览）",
        110: "POP3（邮件接收）",
        123: "NTP（网络时间协议）",
        135: "RPC（远程过程调用）服务",
        139: "NetBIOS会话服务 (SMB over NBT)",
        143: "IMAP（邮件接收）",
        161: "SNMP（简单网络管理协议）",
        194: "IRC（互联网中继聊天）",
        220: "IMAP3（邮件接收）",
        389: "LDAP（轻量级目录访问协议）",
        443: "HTTPS（安全网页浏览）",
        445: "SMB（服务器消息块，用于文件共享）",
        465: "SMTPS（安全邮件发送）",
        514: "Syslog（系统日志）",
        512: "RWHO（远程谁服务）",
        513: "RWHOD（远程谁服务）",
        543: "Kerberos（网络认证协议）",
        554: "RTSP（实时流协议）",
        587: "邮件提交（SMTP提交）",
        631: "CUPS（通用打印系统）",
        902: "VMware管理界面 (HTTPS)",
        912: " VMware VDM（虚拟设备管理器）",
        8080: "HTTP（网页浏览，常用于代理服务器）",
        8443: "HTTPS（安全网页浏览，常用于代理服务器）",
        3306: "MySQL（数据库）",
        5432: "PostgreSQL（数据库）",
        6379: "Redis（键值存储数据库）",
        9200: "Elasticsearch（搜索引擎）",
        11211: "Memcached（缓存系统）"
    }

    def scan_sql_injection(self, url, dbms=None, level=1, risk=1):
        """使用sqlmap进行SQL注入扫描"""
        try:
            # 构建sqlmap命令
            command = [
                'sqlmap', '-u', url, '--dbms', dbms if dbms else '', '--level', str(level), '--risk', str(risk),
                '--batch', '--threads', '1', '--dump'
            ]
            # 执行命令
            process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
            output, _ = process.communicate()
            # 解码输出并记录到日志
            self.log_content += output.decode('utf-8') + '\n'
            print(output.decode('utf-8'))
        except Exception as e:
            self.log_content += f"Error scanning for SQL injection: {e}\n"
            print(f"Error scanning for SQL injection: {e}")
    def capture_network_traffic(self, duration=10):
        """捕获并分析网络流量"""
        try:
            # 捕获网络流量，持续时间为duration秒
            capture = pyshark.LiveCapture(interface='Wi-Fi')  # 修改为你的网络接口，如'eth0'或'Wi-Fi'
            capture.sniff(timeout=duration)  # 捕获持续时间

            # 分析捕获的包
            traffic_info = []
            for packet in capture:
                try:
                    src_ip = packet.ip.src
                    dst_ip = packet.ip.dst
                    protocol = packet.highest_layer
                    length = packet.length
                    info = f"Source: {src_ip}, Destination: {dst_ip}, Protocol: {protocol}, Length: {length}"
                    traffic_info.append(info)
                except AttributeError:
                    continue

            # 将分析结果记录到日志中
            self.log_content += "Network Traffic Analysis:\n"
            for info in traffic_info:
                self.log_content += f"{info}\n"
            print("Network Traffic Analysis:")
            for info in traffic_info:
                print(info)
        except Exception as e:
            self.log_content += f"Error capturing network traffic: {e}\n"
            print(f"Error capturing network traffic: {e}")
    def get_geolocation(self, ip):
        try:
            response = DbIpCity.get(ip, api_key='free')
            geo_info = f"Country: {response.country}, City: {response.city}, Latitude: {response.latitude}, Longitude: {response.longitude}"
            self.log_content += f"Geolocation for {ip}: {geo_info}\n"
            print(f"Geolocation for {ip}: {geo_info}")
        except Exception as e:
            self.log_content += f"Error getting geolocation for {ip}: {e}\n"
            print(f"Error getting geolocation for {ip}: {e}")
    def get_geolocation(self, ip):
        try:
            response = self.geoip_reader.city(ip)
            geo_info = f"Country: {response.country.name}, City: {response.city.name}, Latitude: {response.location.latitude}, Longitude: {response.location.longitude}"
            self.log_content += f"Geolocation for {ip}: {geo_info}\n"
            print(f"Geolocation for {ip}: {geo_info}")
        except Exception as e:
            self.log_content += f"Error getting geolocation for {ip}: {e}\n"
            print(f"Error getting geolocation for {ip}: {e}")
    def nmap_scan(self, ip):
        """使用 nmap 扫描指定的 IP 地址"""
        nm = nmap.PortScanner()
        try:
            nm.scan(ip, '1-1024')  # 扫描 1-1024 端口
            scan_result = nm[ip]['tcp']
            scan_info = f"Nmap scan results for {ip}:\n"
            for port, info in scan_result.items():
                scan_info += f"Port {port}: {info['state']} - {info['name']}\n"
            self.log_content += scan_info  # 将扫描结果添加到日志内容中
        except Exception as e:
            self.log_content += f"Error scanning {ip}: {e}\n"  # 记录错误信息
    def nmap_scan_fast(self, ip):
        """使用 nmap 快速扫描指定的 IP 地址"""
        nm = nmap.PortScanner()
        try:
            nm.scan(ip, arguments='-F')  # 快速扫描
            scan_result = nm[ip]['tcp']
            scan_info = f"Fast scan results for {ip}:\n"
            for port, info in scan_result.items():
                scan_info += f"Port {port}: {info['state']} - {info['name']}\n"
            self.log_content += scan_info  # 将扫描结果添加到日志内容中
        except Exception as e:
            self.log_content += f"Error scanning {ip}: {e}\n"  # 记录错误信息
    def nmap_scan_os(self, ip):
        """使用 nmap 进行操作系统检测"""
        nm = nmap.PortScanner()
        try:
            nm.scan(ip, arguments='-O')  # 操作系统检测
            scan_info = f"OS detection results for {ip}:\n"
            if 'osmatch' in nm[ip]:
                for os in nm[ip]['osmatch']:
                    scan_info += f"OS: {os['name']} (Accuracy: {os['accuracy']}%)\n"
            else:
                scan_info += "OS detection not successful.\n"
            self.log_content += scan_info  # 将扫描结果添加到日志内容中
        except Exception as e:
            self.log_content += f"Error scanning {ip}: {e}\n"  # 记录错误信息
    def nmap_scan_service_version(self, ip):
        """使用 nmap 进行服务版本检测"""
        nm = nmap.PortScanner()
        try:
            nm.scan(ip, arguments='-sV')  # 服务版本检测
            scan_result = nm[ip]['tcp']
            scan_info = f"Service version detection results for {ip}:\n"
            for port, info in scan_result.items():
                scan_info += f"Port {port}: {info['state']} - {info['name']} (Version: {info['version']})\n"
            self.log_content += scan_info  # 将扫描结果添加到日志内容中
        except Exception as e:
            self.log_content += f"Error scanning {ip}: {e}\n"  # 记录错误信息
    def nmap_scan_port_range(self, ip, port_range):
        """使用 nmap 扫描指定的 IP 地址和端口范围"""
        nm = nmap.PortScanner()
        try:
            nm.scan(ip, arguments=f'-p {port_range}')  # 指定端口范围扫描
            scan_result = nm[ip]['tcp']
            scan_info = f"Port range scan results for {ip} ({port_range}):\n"
            for port, info in scan_result.items():
                scan_info += f"Port {port}: {info['state']} - {info['name']}\n"
            self.log_content += scan_info  # 将扫描结果添加到日志内容中
        except Exception as e:
            self.log_content += f"Error scanning {ip}: {e}\n"  # 记录错误信息
    def show_all_subnets_info(self):
        """显示所有子网的全部信息"""
        self.table.clearContents()  # 清空表格内容
        self.table.setRowCount(0)  # 重置行数

        for ip, node_info in self.selected_nodes.items():
            row_position = self.table.rowCount()
            self.table.insertRow(row_position)
            self.table.setItem(row_position, 0, QTableWidgetItem(ip))
            self.table.setItem(row_position, 1, QTableWidgetItem(", ".join(map(str, node_info["open_ports"]))))
            self.table.setItem(row_position, 2, QTableWidgetItem(self.get_node_type(ip)))
            self.table.setItem(row_position, 3, QTableWidgetItem(self.get_link_representation(ip)))
            actions_item = QTableWidgetItem("; ".join([f"{port}: {self.common_ports.get(port, 'Unknown service')}" for port in node_info["open_ports"]]))
            self.table.setItem(row_position, 4, actions_item)
    def get_log_content(self):
        """获取日志内容"""
        return self.log_content

    def get_local_ip(self):
        """动态获取本地主机的IP地址"""
        try:
            # 获取本机的IP地址
            hostname = socket.gethostname()
            local_ip = socket.gethostbyname(hostname)
            return local_ip
        except Exception as e:
            print(f"Error getting local IP: {e}")
            return "127.0.0.1"  # 默认返回本地回环地址
    
    def add_local_host(self):
        """添加本地主机的节点信息"""
        local_ip = self.get_local_ip()
        
        # 使用线程池来并发扫描端口，提高效率
        with ThreadPoolExecutor() as executor:
            # 扫描常见端口范围，这里示例为 1 - 1024，可根据实际需求调整
            future = executor.submit(self.scan_ports, local_ip, "1-1024")
            try:
                local_ports = future.result()  # 获取扫描结果
            except Exception as e:
                print(f"Error scanning local ports: {e}")
                local_ports = []  # 如果扫描出错，设置为空列表

        # 设置本地主机的初始位置（例如画布的中心）
        canvas_width = self.width()
        canvas_height = self.height()
        x = canvas_width // 2 - 12  # 图标的宽度为24，减去一半
        y = canvas_height // 2 - 12  # 图标的高度为24，减去一半
        self.alive_nodes[local_ip] = QPoint(x, y)

        # 初始化本地主机的选中状态、IP地址显示状态和端口信息
        self.selected_nodes[local_ip] = {
            "selected": False,
            "show_text": True,  # 默认显示本地主机的IP地址
            "open_ports": local_ports
        }
        self.on_port_scan_completed(local_ip, local_ports)
        self.update()

    def zoom_in(self):
        """放大画布"""
        self.scale_factor *= 1.2  # 每次点击放大1.2倍
        self.update_transform()

    def zoom_out(self):
        """缩小画布"""
        if self.scale_factor > 0.1:  # 最小缩放因子为0.1
            self.scale_factor /= 1.2  # 每次点击缩小1.2倍
            self.update_transform()

    def update_transform(self):
        """更新缩放变换并重新绘制画布"""
        self.transform.reset()
        self.transform.scale(self.scale_factor, self.scale_factor)
        self.update()  # 强制重绘

    # 辅助函数：获取图标中心点
    def get_center_point(self, point, icon_size=24):
        """根据给定的左上角点和图标大小返回图标中心点"""
        return QPoint(point.x() + icon_size // 2, point.y() + icon_size // 2)

    def apply_forces(self):
        """应用引力、斥力和轻微抖动到主机节点"""
        if self._is_paused:
            return  # 如果暂停，则不应用力
        # 1. 应用主机节点与交换机之间的引力和斥力
        for ip, node_pos in list(self.alive_nodes.items()):
            subnet = ".".join(ip.split('.')[:-1]) + "."
            if subnet in self.switch_positions:
                switch_pos = self.switch_positions[subnet]
                distance = math.sqrt((node_pos.x() - switch_pos.x()) ** 2 + (node_pos.y() - switch_pos.y()) ** 2)

                # 计算方向向量
                dx = switch_pos.x() - node_pos.x()
                dy = switch_pos.y() - node_pos.y()

                if distance > self.max_distance:
                    # 应用引力
                    force_magnitude = self.gravity_constant * (distance - self.max_distance)
                    step_x = min(force_magnitude * dx / distance, self.max_step) if distance != 0 else 0
                    step_y = min(force_magnitude * dy / distance, self.max_step) if distance != 0 else 0
                    new_pos = QPoint(round(node_pos.x() + step_x), round(node_pos.y() + step_y))
                    self.alive_nodes[ip] = new_pos
                    if ip in self.lines:
                        self.lines[ip] = (new_pos, switch_pos)

                elif distance < self.min_distance:
                    # 应用斥力
                    force_magnitude = self.repulsion_constant * (self.min_distance - distance)
                    step_x = min(force_magnitude * dx / distance, self.max_step) if distance != 0 else 0
                    step_y = min(force_magnitude * dy / distance, self.max_step) if distance != 0 else 0
                    new_pos = QPoint(round(node_pos.x() - step_x), round(node_pos.y() - step_y))
                    self.alive_nodes[ip] = new_pos
                    if ip in self.lines:
                        self.lines[ip] = (new_pos, switch_pos)

            # 2. 应用同一子网内主机节点之间的斥力
            # 按子网分组
            nodes_by_subnet = {}
            for ip, pos in self.alive_nodes.items():
                subnet = ".".join(ip.split('.')[:-1]) + "."
                if subnet not in nodes_by_subnet:
                    nodes_by_subnet[subnet] = []
                nodes_by_subnet[subnet].append((ip, pos))
            for subnet, nodes in nodes_by_subnet.items():
                for i, (ip1, pos1) in enumerate(nodes):
                    for j in range(i + 1, len(nodes)):
                        ip2, pos2 = nodes[j]
                        distance = math.sqrt((pos1.x() - pos2.x()) ** 2 + (pos1.y() - pos2.y()) ** 2)
                        if distance < self.node_min_distance:
                            # 计算方向向量并标准化
                            dx = pos2.x() - pos1.x()
                            dy = pos2.y() - pos1.y()
                            norm = math.sqrt(dx * dx + dy * dy)
                            if norm > 0:
                                dx /= norm
                                dy /= norm

                            # 应用斥力
                            force_magnitude = self.node_repulsion_constant * (self.node_min_distance - distance)
                            step_x1 = force_magnitude * dx
                            step_y1 = force_magnitude * dy
                            step_x2 = -step_x1
                            step_y2 = -step_y1

                            # 更新节点位置
                            new_pos1 = QPoint(round(pos1.x() - step_x1), round(pos1.y() - step_y1))
                            new_pos2 = QPoint(round(pos2.x() + step_x2), round(pos2.y() + step_y2))

                            # 确保新位置不会超出画布边界
                            canvas_width = self.width()
                            canvas_height = self.height()
                            icon_size = 24  # 图标的大小
                            new_pos1.setX(max(0, min(canvas_width - icon_size, new_pos1.x())))
                            new_pos1.setY(max(0, min(canvas_height - icon_size, new_pos1.y())))
                            new_pos2.setX(max(0, min(canvas_width - icon_size, new_pos2.x())))
                            new_pos2.setY(max(0, min(canvas_height - icon_size, new_pos2.y())))

                            self.alive_nodes[ip1] = new_pos1
                            self.alive_nodes[ip2] = new_pos2

                            # 更新连线信息（如果需要）
                            if ip1 in self.lines and subnet in self.switch_positions:
                                self.lines[ip1] = (new_pos1, self.get_center_point(self.switch_positions[subnet]))
                            if ip2 in self.lines and subnet in self.switch_positions:
                                self.lines[ip2] = (new_pos2, self.get_center_point(self.switch_positions[subnet]))

        # 3. 应用轻微抖动
        for ip, node_pos in self.alive_nodes.items():
            # 生成随机抖动（使用高斯分布）
            jitter_x = random.gauss(0, self.jitter_magnitude / 3)
            jitter_y = random.gauss(0, self.jitter_magnitude / 3)

            # 确保抖动不会导致节点超出画布边界
            canvas_width = self.width()
            canvas_height = self.height()
            icon_size = 24  # 图标的大小

            new_x = max(0, min(canvas_width - icon_size, node_pos.x() + jitter_x))
            new_y = max(0, min(canvas_height - icon_size, node_pos.y() + jitter_y))

            new_pos = QPoint(round(new_x), round(new_y))
            self.alive_nodes[ip] = new_pos

            # 更新连线信息（如果需要）
            subnet = ".".join(ip.split('.')[:-1]) + "."
            if ip in self.lines and subnet in self.switch_positions:
                self.lines[ip] = (new_pos, self.switch_positions[subnet])
        # 更新路由器与交换机之间的连线
        self.update_router_switch_lines()

    def update_router_switch_lines(self):
        """更新路由器与交换机之间的连线"""
        for subnet, router_position in self.router_positions.items():
            if subnet in self.switch_positions:
                switch_position = self.switch_positions[subnet]
                router_ip = self.router_ips.get(subnet)
                if router_ip and router_ip in self.selected_nodes:
                    if router_ip not in self.router_switch_lines:
                        self.router_switch_lines[router_ip] = {}
                    self.router_switch_lines[router_ip][f"{subnet}254"] = (router_position, switch_position)
    def add_router(self, subnet):
        """添加路由器节点"""
        if subnet in self.router_ips:
            return  # 如果该子网已经有路由器，则不重复添加

        router_ip = f"{subnet}255"  # 构建路由器的IP地址
        canvas_width = self.width()
        canvas_height = self.height()
        icon_size = 24  # 图标的大小

        # 确保canvas_height至少为icon_size
        if canvas_height < icon_size:
            canvas_height = icon_size  # 或者设置一个更大的固定值
        # 确保canvas_width至少为icon_size
        if canvas_width < icon_size:
            canvas_width = icon_size  # 或者设置一个更大的固定值

        # 使用调整后的尺寸进行随机坐标生成
        x = random.randint(0, canvas_width - icon_size)  # 随机横坐标
        y = random.randint(0, canvas_height - icon_size)  # 随机纵坐标
        router_position = QPoint(x + icon_size // 2, y + icon_size // 2)  # 路由器中心位置

        self.router_ips[subnet] = router_ip
        self.router_positions[subnet] = router_position

        # 初始化路由器的选中状态、IP地址显示状态（不显示端口号）
        self.selected_nodes[router_ip] = {"selected": False, "show_text": True, "open_ports": []}

        # 更新画布以显示路由器
        self.update()
        # 检查同网段的交换机，并建立连线
        for switch_subnet, switch_position in self.switch_positions.items():
            if self.is_same_subnet(router_ip, f"{switch_subnet}254"):
                self.lines[router_ip] = (router_position, switch_position)

        self.on_port_scan_completed(router_ip, [])

    def add_switch(self, switch_ip, subnet=None):
        """添加交换机节点"""
        # 设置交换机的随机初始位置
        canvas_width = self.width()
        canvas_height = self.height()
        icon_size = 24
        # 确保canvas_height至少为icon_size
        if canvas_height < icon_size:
            canvas_height = icon_size  # 或者设置一个更大的固定值
        # 确保canvas_width至少为icon_size
        if canvas_width < icon_size:
            canvas_width = icon_size  # 或者设置一个更大的固定值
        # 使用调整后的尺寸进行随机坐标生成
        x = random.randint(0, canvas_width - icon_size)  # 随机横坐标
        y = random.randint(0, canvas_height - icon_size)  # 随机纵坐标
        switch_position = QPoint(x + icon_size // 2, y + icon_size // 2)  # 交换机中心位置

        # 将交换机信息存储在一个字典中，以子网作为键
        if not hasattr(self, 'switch_ips'):
            self.switch_ips = {}
            print(self.switch_ips)
        if not hasattr(self, 'switch_positions'):
            self.switch_positions = {}
        self.switch_ips[subnet] = switch_ip
        self.switch_positions[subnet] = switch_position

        if subnet is None:
            subnet = ".".join(switch_ip.split('.')[:-1]) + "."
            # 更新 node_links，将交换机与其子网内的所有节点相连
            if subnet in self.alive_nodes:
                for node_ip in self.alive_nodes:
                    if self.is_same_subnet(node_ip, switch_ip):
                        if node_ip not in self.node_links:
                            self.node_links[node_ip] = []
                        if switch_ip not in self.node_links[node_ip]:
                            self.node_links[node_ip].append(switch_ip)
                        if switch_ip not in self.node_links:
                            self.node_links[switch_ip] = []
                        if node_ip not in self.node_links[switch_ip]:
                            self.node_links[switch_ip].append(node_ip)
                        node_center = self.alive_nodes[node_ip] + QPoint(24 // 2, 24 // 2)
                        self.lines[node_ip] = (node_center, switch_position)
        # 添加路由器
        self.add_router(subnet)

        # 更新路径表
        if subnet not in self.trace_ll:
            self.trace_ll.append(subnet)

        # 检查路径表长度并创建连线
        if len(self.trace_ll) >= 2:
            for i in range(len(self.trace_ll) - 1):
                subnet1 = self.trace_ll[i]
                subnet2 = self.trace_ll[i + 1]

                router_ip1 = self.router_ips[subnet1]
                switch_ip2 = self.switch_ips[subnet2]

                router_subnet = subnet1
                switch_subnet = subnet2

                if router_subnet in self.router_positions and switch_subnet in self.switch_positions:
                    router_position = self.router_positions[router_subnet]
                    switch_position = self.switch_positions[switch_subnet]

                    # 如果该路由器尚未在 self.router_switch_lines 中，则初始化
                    if router_ip1 not in self.router_switch_lines:
                        self.router_switch_lines[router_ip1] = {}

                    # 如果该连接尚未存在，则添加新的连接线
                    if switch_ip2 not in self.router_switch_lines[router_ip1]:
                        self.router_switch_lines[router_ip1][switch_ip2] = (router_position, switch_position)

        if switch_ip not in self.switch_ips.values() and subnet not in self.switch_positions:
            position = self.get_new_position()
            self.switch_positions[subnet] = position
            self.switch_ips[subnet] = switch_ip

        # 初始化交换机的选中状态、IP地址显示状态（不显示端口号）
        self.selected_nodes[switch_ip] = {"selected": False, "show_text": True, "open_ports": []}
        # 更新画布以显示交换机
        self.update()

        self.on_port_scan_completed(switch_ip, [])
        # 检查并建立路由器与交换机之间的连线
        if subnet in self.router_ips and subnet in self.switch_positions:
            router_ip = self.router_ips[subnet]
            switch_position = self.switch_positions[subnet]
            if router_ip not in self.router_switch_lines:
                self.router_switch_lines[router_ip] = {}
            self.router_switch_lines[router_ip][f"{subnet}254"] = (self.router_positions[subnet], switch_position)


    @pyqtSlot(list)
    def update_trace(self, trace_list, painter=None):
        if trace_list:
            print(trace_list)



    @pyqtSlot(dict)
    def update_alive_nodes(self, nodes):
        """更新存活节点的信息"""
        new_node_detected = False  # 标记是否检测到了新的节点
        icon_size = 24  # 图标的大小

        # 初始化或更新节点的位置
        for ip in nodes:
            if ip not in self.alive_nodes:
                # 新节点，根据IP地址的最后一位初始化位置
                last_octet = int(ip.split('.')[-1])
                x = (last_octet % 20) * 40 + 20  # 横坐标
                y = (last_octet // 20) * 40 + 20  # 纵坐标
                self.alive_nodes[ip] = QPoint(x, y)
                # 初始化新节点的选中状态、IP地址显示状态和端口信息
                self.selected_nodes[ip] = {"selected": False, "show_text": False, "open_ports": []}
                # 异步执行端口扫描
                self.scan_ports(ip)
                # 检查当前子网是否已有交换机
                subnet = ".".join(ip.split('.')[:-1]) + "."
                if subnet not in self.switch_ips:  # 添加一个属性来跟踪已创建的交换机
                    # 如果没有，则为该子网创建一个新的交换机
                    switch_last_octet = 254  # 通常交换机位于子网的最后一个可用IP
                    switch_ips = subnet + str(switch_last_octet)
                    self.add_switch(switch_ips, subnet)
                # 确保新节点与对应子网的交换机连接
                if ip in self.alive_nodes and subnet in self.switch_positions:
                    node_center = self.alive_nodes[ip] + QPoint(icon_size // 2, icon_size // 2)
                    self.lines[ip] = (node_center, self.switch_positions[subnet])
                    # 更新 node_links 以反映新的连接
                    if ip not in self.node_links:
                        self.node_links[ip] = []
                    if subnet in self.switch_ips:
                        switch_ip = self.switch_ips[subnet]
                        if switch_ip not in self.node_links[ip]:
                            self.node_links[ip].append(switch_ip)
                        if switch_ip not in self.node_links:
                            self.node_links[switch_ip] = []
                        if ip not in self.node_links[switch_ip]:
                            self.node_links[switch_ip].append(ip)
                new_node_detected = True

        # 移除不再存在的节点（不移除本地主机）
        for ip in list(self.alive_nodes.keys()):
            if ip not in nodes and ip != self.get_local_ip():
                if ip in self.selected_nodes:
                    del self.selected_nodes[ip]
                del self.alive_nodes[ip]
                if ip in self.lines:
                    del self.lines[ip]
                # 只移除与该节点相关的连接
                if ip in self.node_links:
                    for target_ip in self.node_links[ip]:
                        if ip in self.node_links[target_ip]:
                            self.node_links[target_ip].remove(ip)
                    del self.node_links[ip]

        # 不重置现有节点的选中状态
        self.update()  # 触发重绘

    # 新增：辅助函数，检查两个IP是否属于同一子网
    def is_same_subnet(self, ip1, ip2):
        if not ip1 or not ip2:
            return False
        subnet1 = ".".join(ip1.split('.')[:-1])
        subnet2 = ".".join(ip2.split('.')[:-1])
        return subnet1 == subnet2

    def scan_ports(self, ip, port_range="1-1024"):
        """异步执行端口扫描"""
        task = PortScanTask(ip, port_range, self.on_port_scan_completed)
        self.thread_pool.start(task)

    def update_table_status(self, ip, node_type):
        """更新表格中的Status列"""
        for row in range(self.table.rowCount()):
            if self.table.item(row, 0) and self.table.item(row, 0).text() == ip:
                status_item = QTableWidgetItem(node_type)
                self.table.setItem(row, 2, status_item)
                break

    def on_port_scan_completed(self, ip, open_ports):
        """处理端口扫描完成后的回调"""
        if open_ports is None:
            open_ports = []  # 将 None 转换为空列表
        if ip in self.selected_nodes:
            self.selected_nodes[ip]["open_ports"] = open_ports

            # 查找已知端口对应的描述，并为未知端口提供默认描述
            port_descriptions = [
                f"{port}: {self.common_ports.get(port, 'Unknown service')}"
                for port in open_ports
            ]

            # 检查表格中是否已有该IP地址的行
            row_position = -1
            for row in range(self.table.rowCount()):
                if self.table.item(row, 0) and self.table.item(row, 0).text() == ip:
                    row_position = row
                    break

            # 如果不存在该IP地址的行，则插入新行
            if row_position == -1:
                row_position = self.table.rowCount()
                self.table.insertRow(row_position)

            # 更新 IP 地址和开放端口列表
            self.table.setItem(row_position, 0, QTableWidgetItem(ip))
            self.table.setItem(row_position, 1, QTableWidgetItem(", ".join(map(str, open_ports))))

            # 确定节点类型并更新 Status 列
            node_type = self.get_node_type(ip)
            self.update_table_status(ip, node_type)

            # 更新 Actions 列，使用分号分隔多个服务
            actions_item = QTableWidgetItem("; ".join(port_descriptions))
            self.table.setItem(row_position, 4, actions_item)
            # 更新 Link 列
            link_item = QTableWidgetItem(self.get_link_representation(ip))
            self.table.setItem(row_position, 3, link_item)  # 假设 Link 是第5列
            self.update()  # 重新绘制以反映新的端口信息

    def get_link_representation(self, ip):
        """获取节点的连接表示"""
        if ip in self.node_links:
            links = [f"{ip}->" + target_ip for target_ip in self.node_links[ip]]
            return ", ".join(links)

    def get_node_type(self, ip):
        """确定节点类型"""
        if ip in self.alive_nodes:
            return "主机"
        elif '.'.join(ip.split('.')[3:]) == '254':
            return "交换机"
        else:
            return "路由器"

    def mousePressEvent(self, event):
        """处理鼠标按下事件"""
        if event.button() == Qt.LeftButton:
            # 将鼠标点击的坐标转换为逻辑坐标
            inverted_transform, invertible = self.transform.inverted()
            if invertible:
                logical_pos = inverted_transform.map(event.pos())

            # 检查是否点击了节点或交换机
            for ip, point in self.alive_nodes.items():
                icon_size = 24  # 图标的大小
                rect = QRectF(point.x(), point.y(), icon_size, icon_size)
                if rect.contains(logical_pos):  # 使用逻辑坐标进行碰撞检测
                    # 处理节点拖拽
                    node_state = self.selected_nodes[ip]
                    node_state["selected"] = not node_state["selected"]
                    node_state["show_text"] = node_state["selected"]
                    if node_state["selected"]:
                        self.dragging_node = ip
                        self.drag_start_pos = logical_pos - point  # 使用逻辑坐标计算偏移量
                    else:
                        self.dragging_node = None
                        self.drag_start_pos = None
                    self.update()
                    return

            for subnet, position in self.switch_positions.items():
                if position:  # 确保位置不是None
                    icon_size = 24  # 图标的大小
                    rect = QRectF(position.x() - icon_size // 2, position.y() - icon_size // 2, icon_size, icon_size)
                    if rect.contains(logical_pos):  # 使用逻辑坐标进行碰撞检测
                        # 处理交换机拖拽
                        switch_ip = self.switch_ips.get(subnet)
                        node_state = self.selected_nodes.get(switch_ip,
                                                            {"selected": False, "show_text": True, "open_ports": []})
                        node_state["selected"] = not node_state["selected"]
                        node_state["show_text"] = node_state["selected"]
                        if node_state["selected"]:
                            self.dragging_node = switch_ip
                            self.drag_start_pos = logical_pos - position  # 使用逻辑坐标计算偏移量
                        else:
                            self.dragging_node = None
                            self.drag_start_pos = None
                        self.update()
                        return

            for subnet, router_position in self.router_positions.items():
                if router_position:
                    icon_size = 24
                    rect = QRectF(router_position.x() - icon_size / 2, router_position.y() - icon_size / 2, icon_size,
                                icon_size)
                    if rect.contains(logical_pos):
                        router_ip = self.router_ips[subnet]
                        node_state = self.selected_nodes[router_ip]
                        node_state["selected"] = not node_state["selected"]
                        node_state["show_text"] = node_state["selected"]
                        if node_state["selected"]:
                            self.dragging_node = router_ip
                            self.drag_start_pos = logical_pos - router_position
                        else:
                            self.dragging_node = None
                            self.drag_start_pos = None
                        self.update()
                        return

            # 如果没有点击到节点或交换机，则开始平移
            self.is_panning = True
            self.pan_start_pos = event.pos()

        elif event.button() == Qt.RightButton:
            # 重置拖拽状态
            self.dragging_node = None
            self.drag_start_pos = None

            # 将鼠标点击的坐标转换为逻辑坐标
            inverted_transform, invertible = self.transform.inverted()
            if invertible:
                logical_pos = inverted_transform.map(event.pos())

            # 检查是否点击了节点或交换机
            for ip, point in self.alive_nodes.items():
                icon_size = 24  # 图标的大小
                rect = QRectF(point.x(), point.y(), icon_size, icon_size)
                if rect.contains(logical_pos):  # 使用逻辑坐标进行碰撞检测
                    # 显示右键菜单
                    menu = QMenu(self)
                    check_action = menu.addAction("Check")
                    check_action.triggered.connect(lambda: self.check_subnet(ip))
                    nmap_scan_action = menu.addAction("Nmap scan")
                    nmap_scan_action.triggered.connect(lambda: self.nmap_scan(ip))
                    nmap_scan_action = menu.addAction("Nmap_scan_fast")
                    nmap_scan_action.triggered.connect(lambda: self.nmap_scan_fast(ip))
                    nmap_scan_action = menu.addAction("Nmap_scan_os")
                    nmap_scan_action.triggered.connect(lambda: self.nmap_scan_os(ip))
                    nmap_scan_action = menu.addAction("Nmap_scan_service_version")
                    nmap_scan_action.triggered.connect(lambda: self.nmap_scan_service_version(ip))
                    nmap_scan_action = menu.addAction("Nmap_scan_port_range")
                    nmap_scan_action.triggered.connect(lambda: self.nmap_scan_port_range(ip,"80-1000"))
                    return_action = menu.addAction("Return")
                    return_action.triggered.connect(self.show_all_subnets_info)
                    geograph_action = menu.addAction("Geograph")  # 添加Geograph菜单项
                    geograph_action.triggered.connect(lambda checked, ip=ip: self.get_geolocation(ip))  # 连接到地理定位方法
                    geograph_action = menu.addAction("Geograph_i")  # 添加Geograph菜单项
                    geograph_action.triggered.connect(lambda checked, ip=ip: self.get_geolocation(ip))  # 连接到地理定位方法
                    capture_traffic_action = menu.addAction("Capture Traffic")  # 添加Capture Traffic菜单项
                    capture_traffic_action.triggered.connect(lambda checked, ip=ip: self.capture_network_traffic())  # 连接到网络流量分析方法
                    sql_injection_scan_action = menu.addAction("Scan for SQL Injection")  # 添加SQL注入扫描菜单项
                    sql_injection_scan_action.triggered.connect(lambda checked, ip=ip: self.scan_sql_injection(f"http://{ip}"))  # 连接到SQL注入扫描方法
                    menu.exec_(self.mapToGlobal(event.pos()))
                    return

            for subnet, position in self.switch_positions.items():
                if position:  # 确保位置不是None
                    icon_size = 24  # 图标的大小
                    rect = QRectF(position.x() - icon_size // 2, position.y() - icon_size // 2, icon_size, icon_size)
                    if rect.contains(logical_pos):  # 使用逻辑坐标进行碰撞检测
                        switch_ip = self.switch_ips.get(subnet)
                        # 显示右键菜单
                        menu = QMenu(self)
                        check_action = menu.addAction("Check")
                        check_action.triggered.connect(lambda: self.check_subnet(switch_ip))
                        nmap_scan_action = menu.addAction("Nmap scan")
                        nmap_scan_action.triggered.connect(lambda: self.nmap_scan(switch_ip))
                        nmap_scan_action = menu.addAction("Nmap_scan_fast")
                        nmap_scan_action.triggered.connect(lambda: self.nmap_scan_fast(ip))
                        nmap_scan_action = menu.addAction("Nmap_scan_os")
                        nmap_scan_action.triggered.connect(lambda: self.nmap_scan_os(ip))
                        nmap_scan_action = menu.addAction("Nmap_scan_service_version")
                        nmap_scan_action.triggered.connect(lambda: self.nmap_scan_service_version(ip))
                        nmap_scan_action = menu.addAction("Nmap_scan_port_range")
                        nmap_scan_action.triggered.connect(lambda: self.nmap_scan_port_range(ip,"80-1000"))
                        return_action = menu.addAction("Return")
                        return_action.triggered.connect(self.show_all_subnets_info)
                        geograph_action = menu.addAction("Geograph")  # 添加Geograph菜单项
                        geograph_action.triggered.connect(lambda checked, ip=ip: self.get_geolocation(ip))  # 连接到地理定位方法
                        geograph_action = menu.addAction("Geograph_i")  # 添加Geograph菜单项
                        geograph_action.triggered.connect(lambda checked, ip=ip: self.get_geolocation(ip))  # 连接到地理定位方法
                        capture_traffic_action = menu.addAction("Capture Traffic")  # 添加Capture Traffic菜单项
                        capture_traffic_action.triggered.connect(lambda checked, ip=ip: self.capture_network_traffic())  # 连接到网络流量分析方法
                        sql_injection_scan_action = menu.addAction("Scan for SQL Injection")  # 添加SQL注入扫描菜单项
                        sql_injection_scan_action.triggered.connect(lambda checked, ip=ip: self.scan_sql_injection(f"http://{ip}"))  # 连接到SQL注入扫描方法
                        menu.exec_(self.mapToGlobal(event.pos()))
                        return

            for subnet, router_position in self.router_positions.items():
                if router_position:
                    icon_size = 24
                    rect = QRectF(router_position.x() - icon_size / 2, router_position.y() - icon_size / 2, icon_size,
                                icon_size)
                    if rect.contains(logical_pos):
                        router_ip = self.router_ips[subnet]
                        # 显示右键菜单
                        menu = QMenu(self)
                        check_action = menu.addAction("Check")
                        check_action.triggered.connect(lambda: self.check_subnet(router_ip))
                        nmap_scan_action = menu.addAction("Nmap scan")
                        nmap_scan_action.triggered.connect(lambda: self.nmap_scan(router_ip))
                        nmap_scan_action = menu.addAction("Nmap_scan_fast")
                        nmap_scan_action.triggered.connect(lambda: self.nmap_scan_fast(ip))
                        nmap_scan_action = menu.addAction("Nmap_scan_os")
                        nmap_scan_action.triggered.connect(lambda: self.nmap_scan_os(ip))
                        nmap_scan_action = menu.addAction("Nmap_scan_service_version")
                        nmap_scan_action.triggered.connect(lambda: self.nmap_scan_service_version(ip))
                        nmap_scan_action = menu.addAction("Nmap_scan_port_range")
                        nmap_scan_action.triggered.connect(lambda: self.nmap_scan_port_range(ip,"80-1000"))
                        return_action = menu.addAction("Return")
                        return_action.triggered.connect(self.show_all_subnets_info)
                        geograph_action = menu.addAction("Geograph")  # 添加Geograph菜单项
                        geograph_action.triggered.connect(lambda checked, ip=ip: self.get_geolocation(ip))  # 连接到地理定位方法
                        geograph_action = menu.addAction("Geograph_i")  # 添加Geograph菜单项
                        geograph_action.triggered.connect(lambda checked, ip=ip: self.get_geolocation(ip))  # 连接到地理定位方法
                        capture_traffic_action = menu.addAction("Capture Traffic")  # 添加Capture Traffic菜单项
                        capture_traffic_action.triggered.connect(lambda checked, ip=ip: self.capture_network_traffic())  # 连接到网络流量分析方法
                        sql_injection_scan_action = menu.addAction("Scan for SQL Injection")  # 添加SQL注入扫描菜单项
                        sql_injection_scan_action.triggered.connect(lambda checked, ip=ip: self.scan_sql_injection(f"http://{ip}"))  # 连接到SQL注入扫描方法
                        menu.exec_(self.mapToGlobal(event.pos()))
                        return
    def mouseMoveEvent(self, event):
        """处理鼠标移动事件"""
        if self.dragging_node is not None:
            # 将鼠标移动的坐标转换为逻辑坐标
            inverted_transform, invertible = self.transform.inverted()
            if invertible:
                logical_pos = inverted_transform.map(event.pos())

            # 处理节点或交换机的拖拽
            new_pos = logical_pos - self.drag_start_pos  # 使用逻辑坐标计算新位置
            icon_size = 24  # 图标的大小

            if self.dragging_node in self.alive_nodes:  # 拖拽的是主机节点
                self.alive_nodes[self.dragging_node] = new_pos
                # 更新与该主机节点相关的连线
                subnet = ".".join(self.dragging_node.split('.')[:-1]) + "."
                if subnet in self.switch_positions and self.dragging_node in self.lines:
                    self.lines[self.dragging_node] = (
                    new_pos + QPoint(icon_size // 2, icon_size // 2), self.switch_positions[subnet])

            elif self.dragging_node in self.switch_ips.values():  # 检查是否正在拖拽交换机
                for subnet, switch_ip in self.switch_ips.items():
                    if switch_ip == self.dragging_node:  # 确保位置不是None
                        self.switch_positions[subnet] = new_pos + QPoint(12, 12)  # 交换机中心位置
                        for ip in self.node_links.get(switch_ip, []):
                            if ip in self.lines:
                                self.lines[ip] = (self.alive_nodes[ip] + QPoint(12, 12), self.switch_positions[subnet])
                        # 更新路由器与交换机之间的连线
                        if subnet in self.router_ips:
                            router_ip = self.router_ips[subnet]
                            if router_ip in self.router_switch_lines:
                                self.router_switch_lines[router_ip][f"{subnet}254"] = (self.router_positions[subnet], self.switch_positions[subnet])
                                for subnet1,router_ip1 in self.router_ips.items():
                                    if f"{subnet}254" in self.router_switch_lines[router_ip1]:
                                        self.router_switch_lines[router_ip1][f"{subnet}254"] = (self.router_positions[subnet1], self.switch_positions[subnet])
                        self.update()
                        break

            elif self.dragging_node in self.router_ips.values():
                for subnet, router_ip in self.router_ips.items():
                    if router_ip == self.dragging_node:
                        self.router_positions[subnet] = new_pos + QPoint(12, 12)
                        # 更新路由器与交换机之间的连线
                        if subnet in self.switch_positions:
                            switch_position = self.switch_positions[subnet]
                            if router_ip in self.router_switch_lines:
                                self.router_switch_lines[router_ip][f"{subnet}254"] = (self.router_positions[subnet], switch_position)
                                for subnet1,switch_ip1 in self.switch_ips.items():
                                    if subnet1 != subnet and f"{subnet1}254" in self.router_switch_lines[router_ip]:
                                        self.router_switch_lines[router_ip][f"{subnet1}254"] = (self.router_positions[subnet], self.switch_positions[subnet1])
                        self.update()
                        break
            else:
                self.alive_nodes[self.dragging_node] = new_pos
                node_center = new_pos + QPoint(12, 12)  # 节点中心位置
                if self.dragging_node in self.lines:
                    subnet = ".".join(self.dragging_node.split('.')[:-1]) + "."
                    if subnet in self.switch_positions:
                        self.lines[self.dragging_node] = (node_center, self.switch_positions[subnet])
                self.update()
        elif self.is_panning:
            # 处理画布的平移
            delta = event.pos() - self.pan_start_pos
            self.transform.translate(delta.x(), delta.y())
            self.pan_start_pos = event.pos()
            self.update()
        self.update_router_switch_lines()

    def mouseReleaseEvent(self, event):
        """处理鼠标释放事件"""
        if event.button() == Qt.LeftButton:
            if self.dragging_node is not None:
                self.dragging_node = None
                self.drag_start_pos = None
            elif self.is_panning:
                self.is_panning = False
                self.pan_start_pos = None
    def check_subnet(self, ip):
        """刷新对应节点子网内的信息到表格中"""
        if ip:
            subnet = ".".join(ip.split('.')[:-1]) + "."
            self.update_table_by_subnet(subnet)

    def update_table_by_subnet(self, subnet):
        """根据子网更新表格信息"""
        self.table.clearContents()  # 清空表格内容
        self.table.setRowCount(0)  # 重置行数

        for ip, node_info in self.selected_nodes.items():
            if self.is_same_subnet(ip, subnet):
                row_position = self.table.rowCount()
                self.table.insertRow(row_position)
                self.table.setItem(row_position, 0, QTableWidgetItem(ip))
                self.table.setItem(row_position, 1, QTableWidgetItem(", ".join(map(str, node_info["open_ports"]))))
                self.table.setItem(row_position, 2, QTableWidgetItem(self.get_node_type(ip)))
                self.table.setItem(row_position, 3, QTableWidgetItem(self.get_link_representation(ip)))
    def draw_node_text(self, painter, point, ip, node_state):
        """绘制节点的IP地址和端口号"""
        icon_size = 24  # 图标的大小
        offset_x, offset_y = 15, 5  # 文本相对于图标的偏移量

        # 如果节点的文本应该显示，绘制IP地址和端口号
        if node_state["show_text"]:
            # 绘制 IP 地址
            text_rect = painter.boundingRect(
                point.x() + icon_size + offset_x,
                point.y(),
                100,  # 宽度估计值，可以根据需要调整
                20,  # 高度
                Qt.AlignLeft | Qt.AlignVCenter,
                ip
            )
            painter.drawText(text_rect, Qt.AlignLeft | Qt.AlignVCenter, ip)

            # 绘制端口信息
            ports_text = ", ".join(str(p) for p in node_state["open_ports"])
            ports_rect = painter.boundingRect(
                point.x() + icon_size + offset_x,
                point.y() + 20,  # 端口信息位于IP地址下方
                100,  # 宽度估计值，可以根据需要调整
                20,  # 高度
                Qt.AlignLeft | Qt.AlignVCenter,
                f"Ports: {ports_text}"
            )
            painter.drawText(ports_rect, Qt.AlignLeft | Qt.AlignVCenter, f"Ports: {ports_text}")  # 每50毫秒更新一次位置

    def filter_table_by_subnet(self, subnet):
        """根据子网筛选表格数据"""
        for row in range(self.table.rowCount() - 1, -1, -1):  # 从后往前遍历，方便删除行
            item = self.table.item(row, 0)
            if item:
                ip = item.text()
                if not self.is_same_subnet(ip, subnet):
                    self.table.removeRow(row)    
    def pause_updates(self):
        """暂停更新节点位置"""
        self._is_paused = True

    def resume_updates(self):
        """恢复更新节点位置"""
        self._is_paused = False
        self.update_positions()

    def update_positions(self):
        if not self._is_paused:
            self.apply_forces()
            # 确保本地主机始终连接到其子网的交换机
            local_ip = self.get_local_ip()
            subnet = ".".join(local_ip.split('.')[:-1]) + "."
            if subnet in self.switch_positions and local_ip in self.alive_nodes:
                local_center = self.get_center_point(self.alive_nodes[local_ip])
                switch_position = self.switch_positions[subnet]
                self.lines[local_ip] = (local_center, switch_position)
            self.update()  # 强制重绘

    def paintEvent(self, event):
        """绘制节点和相关信息"""
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing, True)  # 抗锯齿
        painter.setTransform(self.transform)  # 应用缩放和平移变换

        pen = QPen(Qt.red, 2, Qt.SolidLine)
        painter.setPen(pen)
        font = QFont("Arial", 10)  # 设置字体和大小
        painter.setFont(font)
        icon_size = 24  # 图标的大小
        offset_x, offset_y = 15, 5  # 文本相对于图标的偏移量

        # 绘制连线
        for ip, (start_point, end_point) in self.lines.items():
            if ip not in self.router_ips.values():  # 只绘制非路由器相关的连线（主机与交换机的连线）
                painter.drawLine(start_point, end_point)

        # 绘制存活节点
        for ip, point in self.alive_nodes.items():
            pixmap = self.icon.pixmap(icon_size, icon_size)
            painter.drawPixmap(point.x(), point.y(), pixmap)
            node_state = self.selected_nodes.get(ip, {"selected": False, "show_text": False, "open_ports": []})
            if node_state["selected"]:
                pen.setColor(Qt.red)  # 边框颜色
                pen.setWidth(2)  # 边框宽度
                painter.setPen(pen)
                painter.drawRect(point.x(), point.y(), icon_size, icon_size)
                # pen.setColor(Qt.black)  # 恢复默认笔触颜色
                pen.setWidth(2)
                painter.setPen(pen)
            self.draw_node_text(painter, point, ip, node_state)

        # 绘制交换机
        for subnet, position in self.switch_positions.items():  # 遍历交换机的位置
            if position:  # 确保位置不是None
                switch_ip = self.switch_ips.get(subnet)
                pixmap = self.switch_icon.pixmap(icon_size, icon_size)
                painter.drawPixmap(position.x() - icon_size // 2, position.y() - icon_size // 2, pixmap)
                node_state = self.selected_nodes.get(switch_ip,
                                                     {"selected": False, "show_text": True, "open_ports": []})
                if node_state["selected"]:
                    pen.setColor(Qt.red)  # 边框颜色
                    pen.setWidth(2)  # 边框宽度
                    painter.setPen(pen)
                    painter.drawRect(position.x() - icon_size // 2, position.y() - icon_size // 2, icon_size, icon_size)
                    # pen.setColor(Qt.black)  # 恢复默认笔触颜色
                    pen.setWidth(2)
                    painter.setPen(pen)
                self.draw_node_text(painter, position, switch_ip, node_state)

        # 绘制路由器
        for subnet, position in self.router_positions.items():
            if position:
                router_ip = self.router_ips.get(subnet)
                pixmap = self.router_icon.pixmap(icon_size, icon_size)
                painter.drawPixmap(position.x() - icon_size // 2, position.y() - icon_size // 2, pixmap)
                node_state = self.selected_nodes.get(router_ip,
                                                     {"selected": False, "show_text": True, "open_ports": []})
                if node_state["selected"]:
                    pen.setColor(Qt.red)
                    pen.setWidth(2)
                    painter.setPen(pen)
                    painter.drawRect(position.x() - icon_size // 2, position.y() - icon_size // 2, icon_size, icon_size)
                    # pen.setColor(Qt.black)
                    pen.setWidth(2)
                    painter.setPen(pen)
                self.draw_node_text(painter, position, router_ip, node_state)

                # 绘制路由器与交换机之间的连线
                for router_ip, connected_switches in self.router_switch_lines.items():
                    for switch_ip, (router_pos, switch_pos) in connected_switches.items():
                        painter.drawLine(router_pos, switch_pos)


class PortScanTask(QRunnable):
    """端口扫描任务类"""

    def __init__(self, ip, port_range, callback):
        super().__init__()
        self.ip = ip
        self.port_range = port_range  # 添加 port_range 参数
        self.callback = callback

    def run(self):
        """执行端口扫描"""
        # 执行端口扫描，传递 target_ip 和 port_range
        # open_ports = port_scan(self.ip, self.port_range)
        open_ports = asyncio.run(scan_ports(self.ip, port_range=None,
                                            specific_ports=[20, 21, 22, 23, 25, 53, 67, 68, 80, 110, 123, 143, 161, 194,
                                                            220, 389, 443, 445, 465, 514, 512, 513, 543, 554, 587, 631,
                                                            8080, 8443, 3306, 5432, 6379, 9200, 11211]))
        # 调用回调函数，传递扫描结果
        self.callback(self.ip, open_ports)